const WorksheetTemplate = require('./worksheet-template');
const SharedStrings = require('../utils/shared-strings');
const XmlStream = require('../utils/xml-stream');
const fs = require('fs');
const SharedStringsXform = require('../xlsx/xform/strings/shared-strings-xform');

const initial = undefined;
const started = 1;

class TemplateContext {
  constructor(template) {
    this.template = template;
    this.sharedStrings = new SharedStrings();
    this.states = Object.create(null);
    this.xmlStreams = Object.create(null);
    this.rowNos = Object.create(null);
  }

  getXmlStream(id) {
    return this.xmlStreams[id] || (this.xmlStreams[id] = new XmlStream(true));
  }

  render(id, data) {
    if (arguments.length === 1) {
      data = id;
      id = 1;
    }
    const sheetTemplate = this.template.getWorksheet(id);
    const context = {
      sharedStrings: this.sharedStrings,
      xmlStream: this.getXmlStream(id)
    };

    if (this.states[id] === initial) {
      this.states[id] = started;
      this.rowNos[id] = sheetTemplate.start;
      sheetTemplate.renderHeader(context);
    }

    if (Array.isArray(data)) {
      data.forEach(item => {
        context.data = item;
        context.rowNo = this.rowNos[id];
        this.rowNos[id] += sheetTemplate.render(context);
      });
    } else {
      context.data = data;
      context.rowNo = this.rowNos[id];
      this.rowNos[id] += sheetTemplate.render(context);
    }
    return this;
  }

  async write(dest, options) {
    this.writeSharedStrings();
    this.writeTails();

    const { zip } = this.template;
    this.template.sheets.forEach(sheet => {
      zip.appendDisposable(this.getXmlStream(sheet.id).xml, { name: `xl/worksheets/sheet${sheet.id}.xml` });
    });

    if (typeof dest === 'string') {
      dest = fs.createWriteStream(dest);
    }
    const archiver = zip.toArchiver(options);
    archiver.pipe(dest);

    return new Promise((resolve, reject) => {
      archiver.on('error', (err) => {
        zip.clearDisposable();
        reject(err);
      });
      dest.on('error', (err) => {
        zip.clearDisposable();
        reject(err);
      });
      dest.on('finish', () => {
        resolve();
        zip.clearDisposable();
      });
      archiver.finalize();
    });
  }

  writeTails() {
    this.template.sheets.forEach(sheet => sheet.renderTail({ xmlStream: this.getXmlStream(sheet.id) }));
  }

  writeSharedStrings() {
    if (this.sharedStrings.count) {
      const zip = this.template.zip;
      const sharedStringsXform = new SharedStringsXform(this.sharedStrings);
      const xmlStream = new XmlStream(true);
      sharedStringsXform.render(xmlStream);
      zip.appendDisposable(xmlStream.xml, { name: "xl/sharedStrings.xml" });
    }
  }
}

class WorkbookTemplate {
  constructor(zip) {
    this.zip = zip;
    this.sheets = [];
  }

  addWorksheet(options) {
    const id = this.sheets.length + 1;
    if (!options.name) {
      options.name = `sheet${id}`;
    }
    const sheet = new WorksheetTemplate(options);
    this.sheets.push(sheet);
    return sheet;
  }

  getWorksheet(id) {
    if (typeof id === 'number') {
      return this.sheets[id - 1];
    } else {
      return this.sheets.find(sheet => sheet.name === id);
    }
  }

  preRender(ctx) {
    this.sheets.forEach(sheet => sheet.preRender(ctx));
  }

  prepare() {
    return new TemplateContext(this);
  }
}

module.exports = WorkbookTemplate;
